package com.gitee.wsl.struct.generator.sample

import com.gitee.wsl.ext.coroutines.synchronize
import com.gitee.wsl.ext.base.currentTimeMillis
import kotlinx.atomicfu.locks.SynchronizedObject


/**
 * warm 生成15位有序id
 *
 * @author HUANGJIANSHISHENG
 */
class SnowFlakeId15(machineId: Long): SynchronizedObject() {
    // 开始时间戳 (任意设置，建议为项目的开始时间)
    private val epoch = 1609459200000L // 2021-01-01 00:00:00

    // 各部分的位数
    private val sequenceBits = 6L // 序列号占用6位
    private val machineIdBits = 4L // 机器ID占用4位

    // 各部分的最大值
    private val maxMachineId = -1L xor (-1L shl machineIdBits.toInt())
    private val maxSequence = -1L xor (-1L shl sequenceBits.toInt())

    // 各部分的偏移量
    private val sequenceShift: Long = 0
    private val machineIdShift = sequenceBits
    private val timestampShift = machineIdBits + sequenceBits

    private val machineId: Long
    private var sequence = 0L
    private var lastTimestamp = -1L

    init {
        require(!(machineId > maxMachineId || machineId < 0)) { "Machine ID is out of bounds." }
        this.machineId = machineId
    }


    fun nextId(): Long = synchronize{
        var timestamp = timeGen()

        if (timestamp < lastTimestamp) {
            throw RuntimeException("Clock moved backwards. Refusing to generate id.")
        }

        if (timestamp == lastTimestamp) {
            // 当前毫秒内，则+1
            sequence = (sequence + 1) and maxSequence
            if (sequence == 0L) {
                timestamp = tilNextMillis(lastTimestamp)
            }
        } else {
            sequence = 0L
        }

        lastTimestamp = timestamp

        // 生成ID并保证其为15位
        val id = (((timestamp - epoch) shl timestampShift.toInt())
                or (machineId shl machineIdShift.toInt())
                or sequence)

        return@synchronize id % 10000000000000000L // 保证生成的ID为15位
    }

    private fun tilNextMillis(lastTimestamp: Long): Long {
        var timestamp = timeGen()
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen()
        }
        return timestamp
    }

    private fun timeGen() = currentTimeMillis

}
